﻿/*******************************************************************************************
 * Copyright (c) 2009 Alex Robson, Rob Simmons, Craig Israel, and Rachel Twyford
 *
 * Use of this software in any form requires that you have both 
 * read and agreed to the following terms:
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions: 
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the 
 * documentation and/or other materials provided with the distribution.
 * Neither the name of Nvigorate nor the names of its contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************************/


using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.Serialization;
using Nvigorate.Common;
using Nvigorate.Data.Query;

namespace Nvigorate.Data.Query
{
    [XmlInclude(typeof(CriteriaClause))]
    [XmlInclude(typeof(List<object>))]
    [Serializable]
    [XmlRoot("criteria")]
    public class SearchCriteria : List<CriteriaClause>, IXmlSerializable, ICloneable, IDisposable, IQueryObject
    {
        #region Methods



        public SearchCriteria And(CriteriaClause clause)
        {
            if(Count > 0)
            {
                clause.Conjunction = Conjunctions.And;
            }
            else
            {
                clause.Conjunction = Conjunctions.None;
                clause.Criteria.First().Conjunction = Conjunctions.None;
            }
            Add(clause);
            return this;
        }

        public SearchCriteria Or(CriteriaClause clause)
        {
            if (Count > 0)
            {
                clause.Conjunction = Conjunctions.Or;
            }
            else
            {
                clause.Conjunction = Conjunctions.None;
                clause.Criteria.First().Conjunction = Conjunctions.None;
            }
            Add(clause);
            return this;
        }

        /// <summary>
        /// Recursively finds all children criterion and aggregates them into a single collection
        /// </summary>
        /// <returns></returns>
        public List<Criterion> GetCriterion()
        {
            List<Criterion> criterion = new List<Criterion>();

            Action<IClause, List<Criterion>> getFromICriterion;
            Action<CriteriaClause, List<Criterion>> getFromClause = (c, list) => list.Clear();

            getFromICriterion = (ic, list) =>
                                    {
                                        if (ic.GetType().Equals(typeof(CriteriaClause)))
                                            getFromClause((CriteriaClause)ic, list);
                                        else
                                            list.Add((Criterion)ic);
                                    };
            getFromClause = (c, list) => c.Criteria.ForEach(ic => getFromICriterion(ic, list));

            ForEach(c => getFromClause(c, criterion));

            return criterion;
        }

        public override string ToString()
        {
            Queue<CriteriaClause> copy = new Queue<CriteriaClause>();

            foreach (CriteriaClause clause in this)
                copy.Enqueue(clause);

            DelimitedBuilder builder = new DelimitedBuilder(" ");
            
            while (copy.Count > 0)
            {
                CriteriaClause clause = copy.Dequeue();

                string conjunction = "";
                if (clause.Conjunction != Conjunctions.None && builder.ToString() != "")
                    conjunction = (clause.Conjunction == Conjunctions.And) ? "AND" : "OR";

                if (conjunction != "")
                    builder.AppendFormat("{0} ({1})", conjunction, clause.ToString());
                else
                    builder.AppendFormat("{0}", clause.ToString());
            }

            var criteria = builder.ToString();
            if(criteria.StartsWith("AND "))
                criteria = criteria.Substring(4);

            return criteria;
        }
        #endregion

        #region IXmlSerializable Members

        ///<summary>
        ///This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute" /> to the class.
        ///</summary>
        ///
        ///<returns>
        ///An <see cref="T:System.Xml.Schema.XmlSchema" /> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)" /> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)" /> method.
        ///</returns>
        ///
        public XmlSchema GetSchema()
        {
            return null;
        }

        ///<summary>
        ///Generates an object from its XML representation.
        ///</summary>
        ///
        ///<param name="reader">The <see cref="T:System.Xml.XmlReader" /> stream from which the object is deserialized. </param>
        public void ReadXml(XmlReader reader)
        {
            XElement root = XElement.Parse(reader.ReadOuterXml());
            AddRange(root.Elements("clause").Select(x => new CriteriaClause(x)));
        }

        ///<summary>
        ///Converts an object into its XML representation.
        ///</summary>
        ///
        ///<param name="writer">The <see cref="T:System.Xml.XmlWriter" /> stream to which the object is serialized. </param>
        public void WriteXml(XmlWriter writer)
        {
            ForEach(c => c.GetXElementRepresentation().WriteTo(writer));
        }

        public XElement GetXElementRepresentation()
        {
            return new XElement("criteria", this.Select(c => c.GetXElementRepresentation()));
        }

        public object ParseXElement(XElement element)
        {
            AddRange(element.Elements("clause").Select(x => new CriteriaClause(x)));
            return this;
        }

        #endregion

        #region Operators

        public static implicit operator SearchCriteria(CriteriaClause clause)
        {
            return new SearchCriteria(clause);
        }

        public static implicit operator CriteriaClause(SearchCriteria criteria)
        {
            List<IClause> criteriaList = new List<IClause>();
            criteria.ForEach(c => criteriaList.Add(c));
            return new CriteriaClause(criteriaList);
        }

        public static implicit operator SearchCriteria(Criterion criterion)
        {
            return new SearchCriteria(criterion);
        }

        #endregion

        #region Implementation of ICloneable

        public object Clone()
        {
            SearchCriteria clone = new SearchCriteria();
            this.ForEach(c => clone.Add((CriteriaClause)c.Clone()));
            return clone;
        }

        #endregion

        #region Constructors

        public SearchCriteria()
        {
        }

        public SearchCriteria(CriteriaClause clause)
        {
            clause.Conjunction = Conjunctions.None;
            var first = clause.Criteria.FirstOrDefault();
            if(first != null)
                first.Conjunction = Conjunctions.None;
            Add(clause);
        }

        public SearchCriteria(Criterion criterion)
        {
            criterion.Conjunction = Conjunctions.None;
            Add(new CriteriaClause(criterion));
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            Clear();
        }

        #endregion

    }
}